#ifdef WITH_OPENGL
#pragma once

#include <map>
#include <iostream>

#include "opengl.h"
#include "vertex_buffer.h"
#include "context_opengl.h"
#include "renderer.h"
#include "vertex_binding_opengl.h"
#include "vertex.h"

namespace krryn{
namespace scene{
	template
	<
		typename _Vertex
	>
	class vertex_buffer<system::opengl_tag, _Vertex> : public hardware_buffer{
	public:
		static const bool m_IsVertexBuffer = true;

		typedef _Vertex vertex_type;
		typedef typename _Vertex::description_type description_type;
		typedef krryn::core::refc_ptr<vertex_buffer> ptr;

		vertex_buffer(system::renderer<system::opengl_tag> &a_Renderer, size_t a_Size)
			: m_Context(a_Renderer.get_context()), m_Size(a_Size), 
			  m_BufferId(0), m_MappedBuffer(0), m_Count(0){

			glGenBuffersARB(1, &m_BufferId);
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_BufferId);
			glBufferDataARB(
				GL_ARRAY_BUFFER_ARB, sizeof(_Vertex) * a_Size,
				0, GL_STATIC_DRAW_ARB /// TODO: make configurable
			);
		}

		void lock(lock_type a_Type){
			hardware_buffer::lock(a_Type);

			bind();

			if(locked() != read){
				map();
			}
		}

		void unlock(){
			hardware_buffer::unlock();

			if(locked() != read){
				unmap();
			}
			unbind();
		}

		void draw(){
			if(locked() != read){ // write / read test
				throw scene_exception("Buffer must be read only-locked for drawing");
			}

			// GL_TRIANGLES should be dependant upon rendering options
			glDrawArrays(GL_TRIANGLES, 0, (GLsizei)m_Size);
		}

		void draw_indexed(const index_buffer<system::opengl_tag> &a_Buffer){
			if(locked() != read){
				throw scene_exception("Buffer must be read only-locked for drawing");
			}

			glDrawElements(GL_TRIANGLES, (GLsizei)a_Buffer.size(), GL_UNSIGNED_INT, 0);
		}

		void operator +=(_Vertex &a_Vertex){
			if(locked() ^ write){
				throw scene_exception("Buffer must be write-locked for insertion");
			}

			if(m_Count >= m_Size){
				throw scene_exception("Buffer out of bounds");
			}

			/// TODO: create a correct copy of _Vertex such that it copies 
			///   'positions', 'uvs' et cetera instead of raw bytes.

			// copy data onto memory mapped buffer and advance it
			// data is allocated in multiples of sizeof(_Vertex) so this is ok.
			//memcpy(m_MappedBuffer, &a_Vertex, sizeof(_Vertex));

			a_Vertex.copy_into(
				(char*)m_MappedBuffer, 
				(char*)m_MappedBuffer + sizeof(_Vertex));

			m_MappedBuffer += sizeof(_Vertex);
			m_Count++;
		}

	private:
		void bind(){
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, m_BufferId);

			// template specialization makes sure we only bind as needed :-)
			m_Position.bind(m_Context);
			m_Color.bind(m_Context);
			m_Normal.bind(m_Context);
			m_UV.bind(m_Context);
		}

		void unbind(){
			glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
		}

		void map(){
			m_Count = 0; // counter used for error checking
			GLenum l_Mode = -1;

			if(locked() == read)
				l_Mode = GL_READ_ONLY_ARB;
			else if(locked() == write)
				l_Mode = GL_WRITE_ONLY_ARB;
			else if(locked() == read_write)
				l_Mode = GL_READ_WRITE_ARB;

			m_Start = m_MappedBuffer = (GLbyte*)glMapBufferARB(
				GL_ARRAY_BUFFER_ARB, l_Mode);
		}

		void unmap(){
			//_Vertex *a = (_Vertex*)m_Start;

			//for(size_t i = 0; i < m_Size; i++){
			//	//std::cout << i << " : " << a[i] << std::endl;
			//	std::cout << a[i].get_position() << std::endl;
			//	//std::cout << a[i].get_uv()[1] << std::endl;
			//	float *a_Buffer = a[i].get_uv();
			//	for(int j = 0; j < a[i].coord_count(0); j++){
			//		std::cout << (a_Buffer[j]) << ", ";
			//	}

			//	std::cout << std::endl << "c: " << description_type::m_TexCoords << std::endl;
			//}

		/*	for(int q = 0; q < m_Size; q++){
				char *a = (char*)m_Start+(q*sizeof(_Vertex));
				for(int i = 0; i < 24; i++){
					float *b = (float*)(a + i);
					std::cout << i << ": " << *b << std::endl;
				}

				std::cout << std::endl;
			}*/

			glUnmapBufferARB(GL_ARRAY_BUFFER_ARB);
			m_MappedBuffer = 0;
		}

		/// the VBO id
		GLuint m_BufferId;

		/// bindings (e.g. gl...Pointer() calls) that are statically 
		/// bound at compile time
		system_imp::bind_position_opengl<description_type> m_Position;
		system_imp::bind_color_opengl<description_type> m_Color;
		system_imp::bind_normal_opengl<description_type> m_Normal;
		system_imp::bind_uv_opengl<description_type> m_UV;

		/// the context object, responsible for managing state changes
		system_imp::context_opengl &m_Context;

		/// the number of vertices in the buffer
		const size_t m_Size;

		/// the current write position in the buffer (used by operator+=)
		/// for bound checking
		size_t m_Count;

		/// the memory mapped VBO buffer
		GLbyte *m_MappedBuffer;
		GLbyte *m_Start;
	};
}
}

#endif